/**
 * Copyright (c) 2022 Presto Labs Pte. Ltd.
 * Author: Ju
 */

#pragma once

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <memory>
#include <string>
#include <utility>

#include "coin2/base/memory/mem_buf.h"

namespace coin2::base::memory {

// shared  buffer
class MemSlice {
 public:
  explicit MemSlice(const MemBuf *buffer = nullptr, uint32_t size = 65536);

  MemSlice(const MemSlice &);
  MemSlice(MemSlice &&);

  inline ~MemSlice() {
    if (buffer_) {
      buffer_->Release();
    }
  }

  MemSlice &operator=(const MemSlice &);
  MemSlice &operator=(MemSlice &&);

  MemSlice &operator=(const MemBuf *buf);

  bool UpdateBegin(uint32_t off_set);

  const MemBuf *GetMemBuf() const;

  bool Empty() const;

  // Sets the absolute position
  const void *SetBeginPointer(uint32_t pos);
  const void *SetEndPointer(uint32_t pos);

  // Adjusts the pointer based on the current position
  const void *AdjustBeginPointer(int32_t offset);
  const void *AdjustEndPointer(int32_t offset);

  // Sets the relative position of end to begin
  const void *SetRelativeEndPointer(uint32_t length);

  void Reset(const MemBuf *buffer = nullptr);

  const void *Begin() const;
  const void *End() const;
  uint32_t GetUsedSize() const;

  // Clone a complete underlying |MemBuf|, and set the begin and end positions.
  MemSlice Clone() const;

  // Create a copy of the sliced memory.
  MemBuf *CloneSlicedMem() const;

  std::string ToDebugString() const;

 private:
  void Initialize(const MemBuf *buffer);

 public:
  const MemBuf *buffer_;

  uint32_t begin_offset_;
  uint32_t end_offset_;
};

inline MemSlice::MemSlice(const MemBuf *buffer, uint32_t size) {
  begin_offset_ = 0;
  buffer_ = buffer;

  if (buffer) {
    buffer->AddRef();
    end_offset_ = (std::min)(size, buffer->Capacity());
  } else {
    end_offset_ = 0;
  }
}

inline MemSlice::MemSlice(const MemSlice &rhs) {
  begin_offset_ = rhs.begin_offset_;
  end_offset_ = rhs.end_offset_;
  buffer_ = rhs.buffer_;

  if (buffer_) {
    buffer_->AddRef();
  }
}

inline MemSlice::MemSlice(MemSlice &&rhs) {
  buffer_ = rhs.buffer_;
  begin_offset_ = rhs.begin_offset_;
  end_offset_ = rhs.end_offset_;

  rhs.buffer_ = nullptr;
  rhs.begin_offset_ = 0;
  rhs.end_offset_ = 0;
}

inline void MemSlice::Initialize(const MemBuf *buffer) {
  begin_offset_ = 0;
  buffer_ = buffer;

  if (buffer) {
    buffer->AddRef();
    end_offset_ = buffer->Capacity();
  } else {
    end_offset_ = 0;
  }
}

inline MemSlice &MemSlice::operator=(const MemSlice &rhs) {
  if (this == &rhs) {
    return *this;
  }

  if (buffer_) {
    buffer_->Release();
  }

  buffer_ = rhs.buffer_;
  if (buffer_) {
    buffer_->AddRef();
  }

  begin_offset_ = rhs.begin_offset_;
  end_offset_ = rhs.end_offset_;

  return *this;
}

inline MemSlice &MemSlice::operator=(MemSlice &&rhs) {
  if (this == &rhs) {
    return *this;
  }

  if (buffer_) {
    buffer_->Release();
  }

  buffer_ = rhs.buffer_;
  begin_offset_ = rhs.begin_offset_;
  end_offset_ = rhs.end_offset_;

  rhs.buffer_ = nullptr;
  rhs.begin_offset_ = 0;
  rhs.end_offset_ = 0;

  return *this;
}

inline bool MemSlice::UpdateBegin(uint32_t off_set) {
  if (begin_offset_ + off_set > end_offset_) {
    return false;
  }
  begin_offset_ += off_set;
  return true;
}

inline const MemBuf *MemSlice::GetMemBuf() const { return buffer_; }

inline const void *MemSlice::Begin() const {
  return reinterpret_cast<const char *>(buffer_->Begin()) + begin_offset_;
}

inline const void *MemSlice::End() const {
  return reinterpret_cast<const char *>(buffer_->Begin()) + end_offset_;
}

inline uint32_t MemSlice::GetUsedSize() const {
  return static_cast<uint32_t>(end_offset_ - begin_offset_);
}

inline bool MemSlice::Empty() const { return begin_offset_ == end_offset_; }

inline MemSlice &MemSlice::operator=(const MemBuf *buf) {
  if (buffer_) {
    buffer_->Release();
  }

  Initialize(buf);
  return *this;
}

inline const void *MemSlice::SetBeginPointer(uint32_t position) {
  if (position > end_offset_) {
    position = end_offset_;
  }

  begin_offset_ = position;
  return Begin();
}

inline const void *MemSlice::SetEndPointer(uint32_t position) {
  if (position < begin_offset_) {
    position = begin_offset_;
  }

  if (position > buffer_->Capacity()) {
    position = buffer_->Capacity();
  }

  end_offset_ = position;
  return End();
}

inline const void *MemSlice::AdjustBeginPointer(int32_t offset) {
  auto position =
      static_cast<uint32_t>(static_cast<int32_t>(begin_offset_) + offset);

  return SetBeginPointer(position);
}

inline const void *MemSlice::AdjustEndPointer(int32_t offset) {
  auto position =
      static_cast<uint32_t>(static_cast<int32_t>(end_offset_) + offset);

  return SetEndPointer(position);
}

inline const void *MemSlice::SetRelativeEndPointer(uint32_t length) {
  uint32_t end_offset = begin_offset_ + length;
  return SetEndPointer(end_offset);
}

inline void MemSlice::Reset(const MemBuf *buf) {
  if (buffer_) {
    buffer_->Release();
  }

  Initialize(buf);
}

}  // namespace coin2::base::memory